# 导入所需库
import urllib.request  # 用于发送HTTP请求
import re  # 正则表达式处理
from lxml import etree  # HTML/XML解析
import time  # 时间相关操作
import random  # 生成随机数
import json  # JSON数据处理


def clean_chinese(text):
    """清洗中文字符和常见标点
    参数:
        text: 需要清洗的文本
    返回:
        只包含中文和常见标点的文本
    """
    # 使用正则表达式保留中文字符和常见中文标点
    return re.sub(r'[^\u4e00-\u9fa5，。！？【】（）%、]', '', str(text)).strip()


def clear_file():
    """初始化数据文件
    功能:
        清空Data.txt和caipu.json文件内容
    """
    # 清空文本数据文件
    with open('Data2.txt', 'w', encoding='utf-8') as f:
        pass  # 空操作，仅用于清空文件

    # 初始化JSON文件
    with open('caipu.jsonl', 'w', encoding='utf-8') as f:
        pass  # 创建空文件


def get_html():
    """主爬取函数
    功能:
        1. 遍历指定页码范围
        2. 发送HTTP请求获取页面内容
        3. 解析并提取所需数据
        4. 保存数据到文件
    """
    # 基础URL模板
    base_url = 'https://www.zhys.com/caipu/{}.html'

    # 请求头配置
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/91.0.4472.124 Safari/537.36',
        'Referer': 'https://www.zhys.com/'  # 添加来源页防止反爬
    }

    # 爬虫参数设置
    max_retries = 3  # 最大重试次数
    request_delay = 1  # 基础请求延迟(秒)
    collected_data = []  # 数据存储列表(当前未使用)

    # 主循环：遍历0-1000页(实际处理1-6085页)
    for page in range(0, 6084):
        page = page + 1  # 页码从1开始
        url = base_url.format(page)  # 格式化URL
        retry_count = 0  # 当前重试次数
        success = False  # 成功标志

        print(f"\n正在处理数据{page}")  # 打印当前处理页码

        # 重试循环
        while retry_count < max_retries and not success:
            try:
                # 随机延迟(1-1.5秒)避免被封
                time.sleep(request_delay + random.uniform(0, 0.5))

                # 创建请求对象
                req = urllib.request.Request(url=url, headers=headers)

                # 发送请求(15秒超时)
                with urllib.request.urlopen(req, timeout=15) as res:
                    # 检查HTTP状态码
                    if res.status != 200:
                        raise Exception(f"HTTP状态码异常: {res.status}")

                    # 读取并解码HTML内容
                    html = res.read().decode('utf-8')

                    # 使用lxml解析HTML
                    selector = etree.HTML(html)

                    # 使用XPath提取数据
                    # 提取菜谱名称
                    title = selector.xpath('//h1[@class="detail-title"]/span[@class="d-t"]/text()')
                    # 提取图片地址
                    img = selector.xpath('(//div[@class="caipu_head"]//img/@src)')
                    # 提取功效
                    gxtag = selector.xpath('//p[@class="gxtag"]/em/text()')
                    # 提取适用人群
                    c1 = selector.xpath('normalize-space(//li[contains(b,"适宜人群")]/p)')
                    # 提取禁忌人群
                    c2 = selector.xpath('normalize-space(//li[contains(b,"禁忌人群")]/p)')
                    # 提取步骤信息
                    steps = selector.xpath('//div[@class="tujie"]/span/p[position()=2]/text()')
                    #提取步骤对应的图片地址
                    step_images = selector.xpath('//div[@class="tujie"]/span/img/@src')

                    # 数据清洗与异常处理
                    # 处理菜谱名称
                    title = clean_chinese(title[0]) if title and len(title) > 0 else "未知菜谱"
                    # 处理照片文本
                    img_url = ("".join(img)) if img else "无图片"
                    # 处理功效列表
                    gxtag = [clean_chinese(item) for item in gxtag] if gxtag else []
                    # 处理适宜人群文本
                    text = "".join(c1)
                    c1 = re.findall(r'\【(.*?)\】', text) if c1 else []
                    if c1:
                        c1 = c1[0].split('、')
                        c1 = c1[0].split(',')
                    else:
                        c1 = []
                    # 处理禁忌人群文本
                    text = "".join(c2)
                    c2 = re.findall(r'\【(.*?)\】', text) if c2 else []
                    if c2:
                        c2 = c2[0].split('、')
                        c2 = c2[0].split(',')
                    else:
                        c2 = []
                    # 处理步骤列表
                    steps = [clean_chinese(item) for item in steps] if steps else []
                    #处理步骤添加顺序标识
                    new_steps = []
                    for index, step in enumerate(steps, start=1):
                        new_step = f"{index}.{step}"
                        new_steps.append(new_step)
                    steps = new_steps

                    # 提取主要食材
                    # 处理食材列表
                    # ingredients = selector.xpath('//ul[@class="cailiaos"]/li/descendant-or-self::*/text()')
                    li_el = selector.xpath('//ul[@class="cailiaos"]/li')
                    ingredients = []
                    for li in li_el:
                        b_text = li.xpath('.//b/text()')[0] if li.xpath('.//b/text()') else ""
                        i_text = li.xpath('.//i/text()')[0] if li.xpath('.//i/text()') else ""

                        res = i_text + ":" + b_text
                        ingredients.append(res)
                    print(ingredients)

                    # 打印提取结果
                    print(f"菜谱名称: {title}")
                    print(f"图片地址: {img_url}")
                    print(f"功效: [{'、'.join([f'\"{item}\"' for item in gxtag])}]")
                    print(f"适用人群: [{'、'.join([f'\'{item}\'' for item in c1])}]")
                    print(f"禁忌人群: [{'、'.join([f'\'{item}\'' for item in c2])}]")
                    print(f"主要食材: [{','.join(ingredients)}]")
                    print(f"做法步骤: {len(steps)}步")
                    img_url = img_url.split("、")
                    # 构建数据结构
                    data_entry = {
                        "name": title,  # 菜谱名称
                        "img_url": img_url,  # 图片地址
                        "effects": gxtag,  # 功效
                        "suitable_group": c1,  # 适宜人群
                        "taboo_group": c2,  # 禁忌人群
                        "ingredients": ingredients,  # 食材列表
                        "steps": []  # 做法步骤
                    }
                    for i in range(len(steps)):
                        step_data = {
                            "step_text": steps[i],
                            "step_image": step_images[i] if i < len(step_images) else "无步骤图片"
                        }
                        data_entry["steps"].append(step_data)
                    # 转换为JSON字符串(保留中文)
                    data = json.dumps(data_entry, ensure_ascii=False)

                    # 写入文本文件(追加模式)
                    with open('Data2.txt', 'a', encoding='utf-8') as f:
                        f.write(
                            f"页码：{page}\n"
                            f"菜谱名称：{title}\n"
                            f"图片地址：{img_url}\n"
                            f"功效：{'、'.join(gxtag)}\n"
                            f"适宜人群：{c1}\n"
                            f"禁忌人群：{c2}\n"
                            f"主要食材：{'、'.join(ingredients)}\n"
                            f"做法步骤：\n" + "\n".join([f"{i + 1}. {step}" for i, step in enumerate(steps)]) + "\n\n"
                        )

                    # 写入JSON文件(追加模式)
                    with open('caipu.jsonl', 'a', encoding='utf-8') as f:
                        f.writelines(data)  # 写入数据
                        f.write('\n')  # 添加换行分隔

                    success = True  # 标记成功
                    print(f"数据 {page} 采集成功")  # 打印成功信息

            except Exception as e:
                # 异常处理
                retry_count += 1  # 增加重试计数
                print(f"第 {page} 页第 {retry_count} 次尝试失败: {str(e)}")

                # 达到最大重试次数
                if retry_count == max_retries:
                    print(f"跳过第 {page} 页")

                    # 记录失败信息
                    with open('Data.txt', 'a', encoding='utf-8') as f:
                        f.write(f"页码：{page} 采集失败\n\n")

                    break  # 跳出重试循环


if __name__ == '__main__':
    """程序主入口"""
    try:
        # 初始化文件
        clear_file()

        # 记录开始时间
        start_time = time.time()

        # 执行主爬取函数
        get_html()

        # 记录结束时间
        end_time = time.time()

        # 打印总耗时
        print(f"\n数据采集完成！总耗时: {end_time - start_time:.2f}秒")

        # 统计采集结果
        try:
            with open('caipu.jsonl', 'r', encoding='utf-8') as f:
                # 计算行数作为数据条数
                data_count = sum(1 for line in f if line.strip())
                print(f"成功采集 {data_count} 条数据")
        except:
            print("无法统计JSON数据，可能格式不正确")

    except KeyboardInterrupt:
        # 处理用户中断(Ctrl+C)
        print("\n用户中断程序执行")

    except Exception as e:
        # 处理其他异常
        print(f"程序运行出错: {str(e)}")
